
# Copyright 2009 David Lee Miller
#
# This file is part of POSSM (Python Open Source Scanning Microscopy).
#
# POSSM is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# POSSM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License         
# along with POSSM.  If not, see <http://www.gnu.org/licenses/>.
#                                                                           
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

from client import wx,os,time,Queue,dt,ws,createtree
from socket import gethostname
from piezomap import PiezoMap,ArrangeImages


class OldImage(object):
	'''A container to send old data in.'''
	TYPE = 'oldimg'
	def __init__(self,data,close=False):
		self.LINE = None
		self.data = data
		self.close = close

class ConsoleCommands(object):
	''' Manages console commands and keyboard interactions for the main program. '''
	switchlist = {90:'z',83:'s',80:'p',67:'c',68:'d',\
	69:'e',27:'esc',342:'f1',343:'f2',344:'f3',345:'f4',\
	317:'up',319:'down',316:'left',316:'right'}
	key_dic = {'f1':wx.WXK_F1,'f2':wx.WXK_F2,'f3':wx.WXK_F3,'f4':wx.WXK_F4,'f5':wx.WXK_F5,'f6':wx.WXK_F6,
	'f7':wx.WXK_F7,'f8':wx.WXK_F8,'f9':wx.WXK_F9,'f10':wx.WXK_F10,'f11':wx.WXK_F11,'f12':wx.WXK_F12,
	'esc':wx.WXK_ESCAPE,'tab':wx.WXK_TAB,'left':wx.WXK_LEFT,'right':wx.WXK_RIGHT,'up':wx.WXK_UP,'down':wx.WXK_DOWN,
	'pgup':wx.WXK_PAGEUP,'pgdn':wx.WXK_PAGEDOWN,'scroll':wx.WXK_SCROLL,'pause':wx.WXK_PAUSE,'del':wx.WXK_DELETE,
	'home':wx.WXK_HOME,'end':wx.WXK_END,'insert':wx.WXK_INSERT}	
	alreadyconn = '$**Already Connected**\n'
	notconn = '$**Not Connected to Host**\n'
	disconn = '$^^Disconnected^^\n'
	sdlink,splink,silink,salink = None,None,None,None
	paramsq,dataq,ivq,auxq,cmdq,saveq = Queue.Queue(),Queue.Queue(),Queue.Queue(),Queue.Queue(),Queue.Queue(),Queue.Queue()
	lineq = Queue.Queue(1)
	commandlist = ['']
	comnum = None
	scanNum = 0
	pid = None
	scanparams = None
	usesend = True
	hotk = False
	pt0 = ws.PORT_CONTROLLER[0]
	pt1 = ws.PORT_CONTROLLER[1]
	pt2 = ws.PORT_CONTROLLER[2]
	pt3 = ws.PORT_CONTROLLER[3]
	pt4 = ws.PORT_CONTROLLER[4]
	
	def __init__(self,widgets):
		self.CheckMessages(None)
		self.chkmess = wx.Timer(self, id=101)
		self.chkmess.Start(500)
		self.Bind(wx.EVT_TIMER, self.CheckMessages, id=101)
		self.grid = widgets['grid']
		self.tree = widgets['tree']
		self.send_mode = widgets['mode']
		self.scan_disp = widgets['display']
		self.console_disp = wx.TextCtrl(self, -1, '', style=wx.TE_MULTILINE|wx.TE_READONLY)
		self.console = wx.TextCtrl(self, -1, '', style=wx.TE_PROCESS_ENTER)
		self.Bind(wx.EVT_TEXT_ENTER, self.consEnter, self.console)
		self.console.Bind(wx.EVT_KEY_DOWN, self.keyControl)
		self.grid.Bind(wx.EVT_KEY_DOWN,self.keyControl2)
		#self.Bind(wx.grid.EVT_GRID_EDITOR_CREATED, self.OnEditorCreated)
		self.tree.Bind(wx.EVT_KEY_DOWN,self.keyControl2)
		self.console_disp.Bind(wx.EVT_SET_FOCUS,self.focusConsole)
		self.Bind(wx.EVT_KEY_DOWN,self.globalHotkey)
		self.send_mode.Bind(wx.EVT_BUTTON,self.toggleSend)
		self.arimages = ArrangeImages()
		self.setSend()
		#print dir(self)
		#self.pmap = PiezoMap(self,'Piezo Map')
		
	def OnEditorCreated(self,event):
		self.cb = event.GetControl()
		event.Skip()
	
	def setSend(self):
		'''
		Resets the caret in the display if send mode is active.
		'''
		if self.usesend: self.console.SetValue('> ')
		else: self.console.SetValue('')
		self.console.SetInsertionPoint(len(self.console.GetValue()))
	
	def toggleSend(self, event):
		'''
		Toggles send mode. If send mode is on, then all console commands are directed at the
		controller. If send mode is turned off, then the user must type send <action> to talk to
		the controller.
		'''
		self.usesend = not self.usesend
		if self.usesend: self.console.SetValue('> ')
		else: self.console.SetValue('')
	
	def consEnter(self, event):
		'''
		This function is the basic switchboard for the program. If a command is received
		via console, keyboard or from a button pushed on the main control panel, then this function
		directs the actions taken by the program.
		'''
		self.value = None
		try:
			command = self.console.GetValue()
			self.com, self.value = command.split(' ',1)
		except: self.com = self.console.GetValue()
		self.console.SetValue('')
		self.commandlist.append(command)
		
		if self.com == 'connect':
			if self.value: self.OnConn(event, host=self.value)
			else: self.OnConn(event)
		elif self.com == 'disconnect':
			self.OnDcon(event)
		elif self.com == 'map':
			self.OnMap(event)
		elif self.com == 'send' or self.com == '>':
			self.sendEnter(event)
		elif self.com == 'isconnected':
			self.isCon(event)
		elif self.com == 'pyscan':
			self.OnPyscan(event)
		elif self.com == 'scan':
			self.OnScan(event)
		elif self.com == 'lines':
			self.console_disp.AppendText('Line # $ ' + str(self.lineq.get()) + '\n')
		elif self.com == 'quit':
			self.OnQuit(event)
		elif self.com == 'kill':
			self.OnKill(event)
		else:
			self.console_disp.AppendText ( 'CMD Rejected $ ' + self.com + ' \n' )
			self.console.SetValue('')
		self.setSend()
		
	def OnConn(self, event, host=gethostname()):
		'''
		Called when connection between the client and controller is requested. A different socket is opened
		for each type of data sent.
		'''
		if self.sdlink and self.splink and self.silink:
			self.console_disp.AppendText(self.alreadyconn)
		else:
			self.splink,self.sp = self.OpenHandler(host,self.pt0,self.paramsq,'Parameter',collect=False)
			self.sdlink,self.sd = self.OpenHandler(host,self.pt1,self.dataq,'Data',lineN=True,saveq=self.saveq)
			self.silink,self.si = self.OpenHandler(host,self.pt2,self.ivq,'IV',collect=False)
			self.salink,self.sa = self.OpenHandler(host,self.pt3,self.auxq,'Aux',collect=False)
			self.sclink,self.sc = self.OpenHandler(host,self.pt4,self.cmdq,'Cmd',cmd=True)
			self.sp.ID = 'Parameter Dispenser'
			self.sd.ID = 'Data Collector'
			self.si.ID = 'IV Dispenser'
			self.sa.ID = 'Aux Dispenser'
	
	def OpenHandler(self, host, port, queue, linkname, collect=True,isdaemon=False,lineN=False,cmd=False, saveq=None):
		'''
		Opens a data handler with the specified options. Each data handler communicates 1 way. Scans and parameters are
		sent to the controller via the Data Dispenser (connects to the open host socket of the controller's data collector). The
		physical data is collected by the controller, thus for data a collector is opened (and a corresponding dispenser on the
		controller side. These collectors and dispensers are threaded objects which run entirely in the background. Each collector
		and dispenser has an associated queue, where it either dumps data or takes from it.
		'''
		link = dt.SocketCommands(port)
		
		if not isdaemon: 
			link.connect(host)
		else: print '%s is a daemon' % linkname
		
		if cmd:
			clsattr = dt.CmdCollector(link,queue,False)
			htype = ' Collector'
		elif collect and not lineN:
			clsattr = dt.DataCollector(link,queue,daemon=isdaemon)
			htype = ' Collector'
		elif collect and lineN:
			clsattr = dt.DataCollector(link,queue,daemon=isdaemon,lineq=self.lineq,saveq=saveq)
			htype = ' Collector'
		else:
			clsattr = dt.DataDispenser(link,queue,daemon=isdaemon)
			htype = ' Dispenser'
		
		mess = '$^^%s Link to %s Established^^\n' % (linkname,host)
		self.console_disp.AppendText(mess)
		return link, clsattr
		
	
	def SetConsole(self,mess):
		'''
		Posts the given message to the console.
		
		'''
		
		if type(mess) == str:
			mess = '$ '+mess
			self.console_disp.AppendText(mess)
		else: raise 'Argument is not a string'
	
	def OnDcon(self, event):
		'''
		When a disconnect is requested, this function closes the open handlers.
		'''
		if self.splink:
			dt.CloseHandler(self.splink,self.sp)
			self.splink = None
		if self.sdlink:
			dt.CloseHandler(self.sdlink,self.sd)
			self.sdlink = None
		if self.silink:
			dt.CloseHandler(self.silink,self.si)
			self.silink = None
		if self.salink:
			dt.CloseHandler(self.salink,self.sa)
			self.salink = None
		if self.sclink:
			dt.CloseHandler(self.sclink,self.sc)
			self.sclink = None
		self.console_disp.AppendText('xx Disconnected xx\n')
	
	def OnMap(self,event):
		self.pmap = PiezoMap(self,'Piezo Map',self.arimages.MASTER)
		#self.pmap._Show(self,'Piezo Map')
	
	def CheckMessages(self,event):
		try:
			while not self.cmdq.empty():
				mess = self.cmdq.get(block=False)
				if mess == 'mode hotkey':
					self.hotk = True
					self.console.SetEditable(False)
				elif mess == 'mode normal':
					self.hotk = False
					self.console.SetEditable(True)
				else: self.console_disp.AppendText('> '+mess+'\n')
		except: pass
	
	def isCon(self, event):
		'''
		Checkes if the necessary sockets are connected.
		'''
		try:
			hostinfo = []
			hostinfo.append(self.splink.SCK.getpeername())
			hostinfo.append(self.sdlink.SCK.getpeername())
			hostinfo.append(self.silink.SCK.getpeername())
			self.console_disp.AppendText('Connected to %s on ports: %d, %d, %d\n' % (hostinfo[0][0],hostinfo[0][1],hostinfo[1][1],hostinfo[2][1]))
		except:
			self.console_disp.AppendText(self.notconn)
	
	def keyControl(self, event):
		'''
		Intercepts keyboard events and manages hotkeys.
		'''
		KEY_PRESSED = event.GetKeyCode()
		#print KEY_PRESSED
		if len(self.commandlist) > 20: self.commandlist.pop(0)
		if KEY_PRESSED in self.switchlist and self.hotk:
			self.auxq.put(self.switchlist[KEY_PRESSED])
			#print KEY_PRESSED
			if self.switchlist[KEY_PRESSED] == 'esc':
				self.console.SetEditable(True)
				self.hotk = False
		elif KEY_PRESSED == wx.WXK_UP:
			if self.comnum == None: self.comnum = len(self.commandlist)-1
			elif self.comnum == 0: pass
			else: self.comnum -= 1
			if self.comnum: self.console.SetValue(self.commandlist[self.comnum])
		elif KEY_PRESSED == wx.WXK_DOWN:
			if self.comnum == None: self.comnum = len(self.commandlist)-1
			elif self.comnum > len(self.commandlist)-1: pass
			else: self.comnum += 1
			if self.comnum == None: self.console.SetValue('')
			elif self.comnum < len(self.commandlist): self.console.SetValue(self.commandlist[self.comnum])
			else: self.console.SetValue('')
		else:
			self.comnum = None
			self.keyControl2(event)
		event.Skip()
	
	def keyControl2(self, event):
		'''
		Further processing of keyboard events.
		'''
		KEY_PRESSED = event.GetKeyCode()
		if KEY_PRESSED == wx.WXK_TAB:
			self.globalHotkey(event)
		if KEY_PRESSED == self.key_dic[ws.DISCONNECT]:
			self.OnDcon(None)
		elif KEY_PRESSED == self.key_dic[ws.CONNECT]: #Sets 'connect' in the console bar
			self.OnConn(None)
		elif KEY_PRESSED == self.key_dic[ws.SCAN_SELECTED]: #Sets the scan in the console bar
			self.StartScan(None)
		elif KEY_PRESSED == self.key_dic[ws.HELP_SELECTED]: #Displays Help Information
			sel = self.tree.GetSelection()
			par = self.tree.GetItemParent(sel)
			parname = str(self.tree.GetItemText(par))
			if parname == 'Root' or parname == '': pass
			else:
				par2 = self.tree.GetItemParent(par)
				parname2 = str(self.tree.GetItemText(par2))
				if parname2 == 'Root' or parname2 == '':
					pos = self.grid.GetGridCursorRow()
					txt = self.grid.GetCellValue(pos,0)
					txt2 = str(self.tree.GetItemText(sel))
					stuff = str(self.newtree.treebytreename[parname].data[txt2][txt][2])
					self.console_disp.AppendText(txt+':: '+stuff+'\n')
				else:
					txt = str(self.tree.GetItemText(sel))
					stuff = str(self.newtree.treebytreename[parname2].data[parname][txt][2])
					self.console_disp.AppendText(txt+':: '+stuff+'\n')
		elif KEY_PRESSED == self.key_dic[ws.SEND_GO]: #Sends go to the controller
			self.sendEnter(None,arg='go')
		elif KEY_PRESSED == self.key_dic[ws.SEND_ABORT]: #Sends abort to the controller
			self.sendEnter(None,arg='abort')
		elif KEY_PRESSED == wx.WXK_TAB:
			self.focusConsole(None)
		elif KEY_PRESSED == wx.WXK_UP:
			wx.CallAfter(self.grid.DisableCellEditControl)
			self.grid.MoveCursorUp(False)
			if not self.grid.IsReadOnly(self.grid.GetGridCursorRow(),self.grid.GetGridCursorCol()): wx.CallAfter(self.grid.EnableCellEditControl)
		elif KEY_PRESSED == wx.WXK_DOWN:
			wx.CallAfter(self.grid.DisableCellEditControl)
			self.grid.MoveCursorDown(False)
			if not self.grid.IsReadOnly(self.grid.GetGridCursorRow(),self.grid.GetGridCursorCol()): wx.CallAfter(self.grid.EnableCellEditControl)
		elif KEY_PRESSED == wx.WXK_LEFT:
			wx.CallAfter(self.grid.DisableCellEditControl)
			self.grid.MoveCursorLeft(False)
			if not self.grid.IsReadOnly(self.grid.GetGridCursorRow(),self.grid.GetGridCursorCol()): wx.CallAfter(self.grid.EnableCellEditControl)
		elif KEY_PRESSED == wx.WXK_RIGHT:
			self.grid.MoveCursorRight(False)
			if not self.grid.IsReadOnly(self.grid.GetGridCursorRow(),self.grid.GetGridCursorCol()): wx.CallAfter(self.grid.EnableCellEditControl)
		elif KEY_PRESSED == wx.WXK_RETURN:
			wx.CallAfter(self.grid.DisableCellEditControl)
			self.grid.MoveCursorDown(False)
			if not self.grid.IsReadOnly(self.grid.GetGridCursorRow(),self.grid.GetGridCursorCol()): wx.CallAfter(self.grid.EnableCellEditControl)
		event.Skip()
	
	def StartScan(self,event):
		sel = self.tree.GetSelection()
		par = str(self.tree.GetItemText(self.tree.GetItemParent(sel)))
		gpar = str(self.tree.GetItemText(self.tree.GetItemParent(self.tree.GetItemParent(sel))))
		if par == 'Root':
			sval = str(self.tree.GetItemText(sel))
			self.value = sval
			self.OnScan(None)
		elif gpar == 'Root':
			sval = str(self.tree.GetItemText(self.tree.GetItemParent(sel)))
			self.value = sval
			self.OnScan(None)
		else: pass
			
	def focusConsole(self,event):
		self.console.SetFocus()
	
	def globalHotkey(self, event):
		KEY_PRESSED = event.GetKeyCode()
		if KEY_PRESSED == wx.WXK_TAB:
			self.focusConsole(None)
		elif KEY_PRESSED == wx.WXK_F1:
			self.console.SetValue('connect')
		else: pass
	
	def OnScan(self, event):
		'''
		When a scan is sent to the controller, this command prepares the scan and data package and
		then places it in the queue to be sent to the controller.
		'''
		if not self.sdlink:
			self.SetConsole('Not Connected to Controller')
		elif self.value in createtree.scanexec.keys():
			self.sd.lineN=0
			self.sd.time = None
			self.scanNum+=1
			createtree.scanexec[self.value].package.__init__()
			createtree.scanexec[self.value].scan.__init__(createtree.scanexec[self.value].package)
			if hasattr(createtree.scanexec[self.value].package,'SCAN_Y'):
				self.scanparams = createtree.scanexec[self.value].package
			else: self.scanparams = None
			self.paramsq.put(createtree.scanexec[self.value].scan)
			createtree.scanexec[self.value]._exec_code(parent=self)
		else: self.console_disp.AppendText('Scan %s cannot be found. \n'%self.value)
	
	def OnKill(self,event):
		if self.pid:
			os.kill(self.pid,1)
			self.pid = None
		else: pass

	
	def OnQuit(self,event):
		pass
	
	def sendEnter(self,event,arg=False):
		'''
		When enter is pressed, the console is cleared and the action is posted in the display box.
		If the user is not connected, then the not connected error is displayed.
		'''
		if self.salink and arg:
			self.auxq.put(str(arg))
			self.console_disp.AppendText('Sent $ ' + str(arg) + '\n')
		elif self.salink:
			self.auxq.put(str(self.value))
			self.console_disp.AppendText('Sent $ ' + self.value + '\n')
			self.console.SetValue('')
		else:
			self.console_disp.AppendText(self.notconn)
